<?php
namespace ITSociaal\Themes\Lumber\Hooks;

use ITSociaal\Themes\Lumber\CreateObjectTrait;

/**
 * Installs hooks based on method names
 */
class Init {

	use CreateObjectTrait;

	/**
	 * Create instances of HooksInterface
	 *
	 * @param string[] $classes
	 *
	 * @return \ITSociaal\Themes\Lumber\Hooks\HooksInterface[]
	 */
	public function createHookers($classes) {
		$testHasSetup = function($hooker) {
			return method_exists($hooker, "setup");
		};

		$ret = array_map(
			[$this, "createObject"],
			$classes
		);

		$haveSetup = array_filter($ret, $testHasSetup);

		array_map(
			"call_user_func",
			array_map(
				"array_merge",
				array_map(
					"array_fill",
					array_fill(0, count($haveSetup), 0),
					array_fill(0, count($haveSetup), 1),
					$haveSetup
				),
				array_fill(0, count($haveSetup), ["setup"])
			)
		);

		return $ret;
	}

	/**
	 * Gather all the hooks, group them by class name and hook type
	 *
	 * @param \ITSociaal\Themes\Lumber\Hooks\HooksInterface[] $hookers
	 *
	 * @return array
	 */
	public function getAllHooks(array $hookers) {
		$eachHook = function(array $carry, array $item) {
			if (count($item) == 3) {
				switch ($item[0]) {
					case "on":
						$type = "action";
						break;
					case "filter":
						$type = $item[0];
						break;
					default:
						return $carry;
				}

				$carry[$type]->{$item[1]} = $this->createObject(
					Hook::class,
					$item[1],
					$item[2]
				);
			}

			return $carry;
		}; // $eachHook

		$eachHooker = function(HooksInterface $hooker) use ($eachHook) {
			$methods = $this->getMethodsFromObject($hooker);

			sort($methods, SORT_STRING);

			$zero = array_fill(0, count($methods), 0);
			$one = array_fill(0, count($methods), 1);

			// It's because of autism
			$hooks = array_reduce(
				array_map(
					"array_merge",
					array_map(
						"explode",
						array_fill(0, count($methods), "_"),
						$this->getHookTagsFromMethods($methods),
						array_fill(0, count($methods), 2)
					),
					array_map(
						"array_fill",
						$zero,
						$one,
						array_map(
							"array_merge",
							array_map(
								"array_fill",
								$zero,
								$one,
								array_fill(0, count($methods), $hooker)
							),
							array_map(
								"array_fill",
								$zero,
								$one,
								$methods
							)
						)
					)
				),
				$eachHook,
				[
					"action" => (object)[],
					"filter" => (object)[],
				]
			);

			if (method_exists($hooker, "adjustHooks")) {
				call_user_func_array([$hooker, "adjustHooks"], $hooks);
			}

			return $hooks;
		}; // $eachHooker

		return array_combine(
			array_map("get_class", $hookers),
			array_map($eachHooker, $hookers)
		);
	}

	/**
	 * Installs all hooks from HooksInterface implementations passed by name
	 *
	 * @param string[]                                 $classes
	 * @param \ITSociaal\Themes\Lumber\Hooks\Init|null $self
	 *
	 * @return \ITSociaal\Themes\Lumber\Hooks\Init
	 */
	public static function installClasses(array $classes, Init $self = null) {
		$self = $self ?: new static();

		return
			$self->installHooks(
				$self->getAllHooks(
					$self->createHookers($classes)
				)
			);
	}

	/**
	 * Pass $hooks to add_action and add_filter
	 *
	 * @param array $hooks
	 *
	 * @return $this
	 */
	public function installHooks(array $hooks) {
		$eachType = function($type, array $hooks) {
			array_map(
				"call_user_func_array",
				array_fill(0, count($hooks), "add_" . $type),
				array_map("get_object_vars", $hooks)
			);
		};

		$eachHooker = function(array $hooks, $index) use ($eachType) {
			array_map(
				$eachType,
				array_keys($hooks),
				array_map("get_object_vars", $hooks)
			);
		};

		array_walk($hooks, $eachHooker);

		return $this;
	}

	/**
	 * Convert camelCase method names to match hook_style_names
	 *
	 * @param string[] $methods
	 *
	 * @return string[]
	 */
	protected function getHookTagsFromMethods(array $methods) {
		$eachChar = function($char) {
			return ctype_upper($char) ? "_" . strtolower($char) : $char;
		};
		$eachMethod = function($method) use ($eachChar) {
			return implode(
				"",
				array_map($eachChar, str_split($method))
			);
		};

		return array_map($eachMethod, $methods);
	}

	/**
	 * Get array of method names for any given $object
	 *
	 * @param object $object
	 *
	 * @return string[]
	 */
	protected function getMethodsFromObject($object) {
		return get_class_methods($object);
	}

}

/* vi:set ts=4 sw=4 noet: */
